
#include <libusb-1.0/libusb.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

#define INIT_USB_CTX if(usb_ctx==0){ \
        libusb_init(&usb_ctx); \
    }

static libusb_context*usb_ctx = 0;
static int lusb_open(lua_State*L);
static int lusb_driver_active(lua_State*L);
static int lusb_detach_kernel_driver(lua_State*L);
static int lusb_attach_kernel_driver(lua_State*L);
static int lusb_set_auto_detach_kernel_driver(lua_State*L);
static int lusb_error_name(lua_State*L);
static int lusb_list(lua_State*L);
static int lusb_claim_interface(lua_State*L);
static int lusb_set_configuration(lua_State*L);
static int lusb_release_interface(lua_State*L);
static int lusb_get_configuration(lua_State*L);
static int lusb_get_device_desc(lua_State*L);
static int lusb_get_config_descriptor(lua_State*L);
static int lusb_bulk_transfer(lua_State*L);
static int lusb_interrupt_transfer(lua_State*L);
static int lusb_control_transfer(lua_State*L);
static int lmem_new(lua_State*L);
static int lmem_value(lua_State*L);

static const struct luaL_Reg libs[] = {
    {"lusb_open",lusb_open},

    {"lusb_list",lusb_list},
    {"lusb_driver_active",lusb_driver_active},
    {"lusb_detach_kernel_driver",lusb_detach_kernel_driver},
    {"lusb_attach_kernel_driver",lusb_attach_kernel_driver},
    {"lusb_set_auto_detach_kernel_driver",lusb_set_auto_detach_kernel_driver},
    {"lusb_error_name",lusb_error_name},

    {"lusb_claim_interface"  ,lusb_claim_interface,},
    {"lusb_set_configuration",lusb_set_configuration,},
    {"lusb_release_interface",lusb_release_interface,},

    {"lusb_get_configuration",lusb_get_configuration},

    {"lusb_get_device_desc",lusb_get_device_desc},
    {"lusb_get_config_descriptor",lusb_get_config_descriptor},

    {"lusb_bulk_transfer",lusb_bulk_transfer},
    {"lusb_interrupt_transfer",lusb_interrupt_transfer},
    {"lusb_control_transfer",lusb_control_transfer},

    {"mem_value",lmem_value},
    {"mem_new",lmem_new},

    {NULL,NULL},
};

int luaopen_lusb(lua_State*L){
    luaL_newlib(L,libs);
    return 1;
}

static int lmem_new(lua_State*L){
    lua_newuserdata(L,luaL_checkinteger(L,1));
    return 1;
}

static int lmem_value(lua_State*L){
    void*ud = lua_touserdata(L,1);
    int offset = luaL_checkinteger(L,2);
    if(ud){
        int sz = 0;
        lua_Integer v = 0;
        if(lua_isinteger(L,3))sz = lua_tointeger(L,3);
        switch (sz)
        {
            case 2: v = *(uint16_t*)(((uint8_t*)ud) + offset);break;
            case 4: v = *(uint32_t*)(((uint8_t*)ud) + offset);break;
            case 8: v = *(uint64_t*)(((uint8_t*)ud) + offset);break;
        default:
            v = *(uint8_t*)(((uint8_t*)ud) + offset);
            break;
        }
        lua_pushinteger(L,v);
        return 1;
    }
    return 0;
}

static int lusb_get_device_desc(lua_State*L){
    void*ud = lua_touserdata(L,1);
    void*dev = libusb_get_device(ud);
    void *desc = //calloc(1,sizeof(struct libusb_device_descriptor));
        lua_newuserdata(L,sizeof(struct libusb_device_descriptor));
    int err = libusb_get_device_descriptor(dev,desc);
    lua_pushlightuserdata(L,desc);
    lua_pushinteger(L,err);
    return 2;
}

static void pushf(int *retlen,lua_State*L,const char*fmt,...){
    va_list ap;
    va_start(ap,fmt);

    char buf[512];
    buf[vsprintf(buf,fmt,ap)] = 0;
    lua_pushstring(L,buf);
    retlen[0] ++;
    va_end(ap);
}

static int lusb_get_config_descriptor(lua_State*L){
    void*ud = lua_touserdata(L,1);
    void*dev = libusb_get_device(ud);
    struct libusb_device_descriptor desc;
    int err = libusb_get_device_descriptor(dev,&desc);
    if(err!=0){
        lua_pushinteger(L,err);
        return 1;
    }
    struct libusb_config_descriptor *config_desc = 0;
    int config = luaL_checkinteger(L,2);
    err = libusb_get_config_descriptor(dev, config, &config_desc);
    if(err!=0){
        lua_pushinteger(L,err);
        return 1;
    }
    int config_num = config_desc->bConfigurationValue;
    const struct libusb_interface_descriptor *intf_desc_candidate = NULL;
    const struct libusb_interface_descriptor *intf_desc_found = NULL;
    int ret_len = 0;

    lua_pushinteger(L,0);       //error
    ret_len++;

    lua_pushinteger(L,config_num = config_desc->bConfigurationValue);
    ret_len++;

    for (int interface = 0; interface < config_desc->bNumInterfaces; interface++) {
        const struct libusb_interface_descriptor *intf_desc = &config_desc->interface[interface].altsetting[0];
        int interface_num = intf_desc->bInterfaceNumber;
        lua_pushstring(L,"==");
        ret_len++;

        //push_str(&ret_len,L,"%s%d"intf_desc->iInterface);
        pushf(&ret_len,L,"interface idx:%d",intf_desc->iInterface);
        pushf(&ret_len,L,"interface num:%d",intf_desc->bInterfaceNumber);
        pushf(&ret_len,L,"packet_size(out):%d",intf_desc->endpoint[0].wMaxPacketSize);
        pushf(&ret_len,L,"packet_size(in):%d",intf_desc->endpoint[1].wMaxPacketSize);
        pushf(&ret_len,L,"endpoint(out):%02x",intf_desc->endpoint[0].bEndpointAddress);
        pushf(&ret_len,L,"endpoint(in):%02x",intf_desc->endpoint[1].bEndpointAddress);

        if (intf_desc->iInterface != 0) {
            char interface_str[256] = {0};
            err = libusb_get_string_descriptor_ascii(ud,intf_desc->iInterface,(uint8_t*)interface_str,sizeof(interface_str));
            if(err<0){
                pushf(&ret_len,L,"err interface");
            }
            else{
                pushf(&ret_len,L,"interface:%s",interface_str);
            }
        }
        else pushf(&ret_len,L,"err interface num");

        lua_pushinteger(L,intf_desc->bNumEndpoints);
        ret_len++;
        static const char* atts[]={
            "ctrl",
            "iso",
            "bulk",
            "int",
        };

        // if({
        //     lua_pushstring(L,atts[intf_desc->endpoint[0].bmAttributes&3]);
        // }
        // else{
        //     lua_pushstring(L,"no_out");
        // }
        // ret_len++;

        pushf(
            &ret_len,L,
            ((intf_desc->endpoint[0].bEndpointAddress & 0x80) == LIBUSB_ENDPOINT_OUT)?"out:%s":"no_out",
            atts[intf_desc->endpoint[0].bmAttributes&3]
            );

        pushf(
            &ret_len,L,
            ((intf_desc->endpoint[1].bEndpointAddress & 0x80) == LIBUSB_ENDPOINT_IN)?"in:%s":"no_in",
            atts[intf_desc->endpoint[1].bmAttributes&3]
            );

        lua_pushstring(L,"|");
        ret_len++;
    }
    libusb_free_config_descriptor(config_desc);

    return ret_len;
}

static libusb_device_handle* usb_list_proc(lua_State*L,int vid,int pid,int *error,int *usb_vid_add_list){
    INIT_USB_CTX;
    struct libusb_device **list;
    struct libusb_device_descriptor desc;
    int cnt = libusb_get_device_list(usb_ctx,&list);
    int i;
    int err;
    int vlist = 0;
    if(cnt<0){
        printf("%s","error!\n");
    }
    for(i=0;i<cnt;i++){
        struct libusb_device *device = list[i];
        err = libusb_get_device_descriptor(device, &desc);
        if (err != LIBUSB_SUCCESS) {
			printf("libusb_get_device_descriptor() failed with %s", libusb_error_name(err));
			continue;
		}
        if(usb_vid_add_list){
            lua_pushinteger(L,desc.idVendor);
            lua_pushinteger(L,desc.idProduct);
            vlist += 2;
        }

        //printf("vid:%04X,pio:%04X\n",desc.idVendor,desc.idProduct);
        if(desc.idVendor==vid&&desc.idProduct==pid){
            libusb_device_handle *h = 0;//libusb_open_device_with_vid_pid(ctx,vid,pid);
            int err = libusb_open(device,&h);
            *error = err;
            libusb_free_device_list(list,1);
            return h;
        }
    }
    libusb_free_device_list(list,1);
    if(usb_vid_add_list) *usb_vid_add_list = vlist;
    return 0;
}

static int lusb_list(lua_State*L){
    int list = 0;
    int err = 0;
    usb_list_proc(L,0,0,&err,&list);
    return list;
}

static int lusb_open(lua_State*L){
    int vid = luaL_checkinteger(L,1);
    int pid = luaL_checkinteger(L,2);

    INIT_USB_CTX;

    libusb_device_handle *dev = libusb_open_device_with_vid_pid(usb_ctx,vid,pid);
    if(!dev){
        int err = -12345;
        void *d = usb_list_proc(L,vid,pid,&err,0);
        if(err==-12345){
            lua_pushnil(L);
            err = 0;
        }else
            lua_pushlightuserdata(L,d);

        lua_pushinteger(L,err);
        return 2;
    }
    lua_pushlightuserdata(L,dev);
    return 1;
}

#define CALL_DRIVER(func) \
    void *ud = lua_touserdata(L,1); \
    int ret = func(ud,luaL_checkinteger(L,2)); \
    lua_pushinteger(L,ret); \
    return 1;

static int lusb_driver_active(lua_State*L){
    CALL_DRIVER(libusb_kernel_driver_active);
}
static int lusb_detach_kernel_driver(lua_State*L){
    CALL_DRIVER(libusb_detach_kernel_driver);
}
static int lusb_attach_kernel_driver(lua_State*L){
    CALL_DRIVER(libusb_attach_kernel_driver);
}

static int lusb_set_auto_detach_kernel_driver(lua_State*L){
    CALL_DRIVER(libusb_set_auto_detach_kernel_driver);
}

#define CALL_INTERFACE(func) \
    void *ud = lua_touserdata(L,1); \
    int ret = func(ud,luaL_checkinteger(L,2)); \
    lua_pushinteger(L,ret); \
    return 1;
static int lusb_claim_interface(lua_State*L){
    CALL_INTERFACE(libusb_claim_interface);
}
static int lusb_set_configuration(lua_State*L){
    CALL_INTERFACE(libusb_set_configuration);
}
static int lusb_release_interface(lua_State*L){
    CALL_INTERFACE(libusb_release_interface);
}

static int lusb_get_configuration(lua_State*L){
    void*ud = lua_touserdata(L,1);
    int config = 0;
    int err = libusb_get_configuration(ud,&config);
    lua_pushinteger(L,config);
    lua_pushinteger(L,err);
    return 2;
}

static int lusb_error_name(lua_State*L){
    lua_pushstring(L,libusb_error_name(luaL_checkinteger(L,1)));
    return 1;
}

static unsigned char read_buf[1024];

static int lusb_bulk_transfer(lua_State*L){
    void*ud = lua_touserdata(L,1);
    int ep = luaL_checkinteger(L,2);
    void*buf = 0;
    if(lua_isuserdata(L,3) || lua_islightuserdata(L,3)){
        buf = lua_touserdata(L,3);
    }
    else if(lua_isstring(L,3) && !(ep&LIBUSB_ENDPOINT_IN)){
        buf = (void*)lua_tostring(L,3);
    }
    else{
        // lua_pushstring(L,"transfer data not supported!");
        // lua_error(L);
        // return 1;
        //直接返回字符串数据
    }
    int len = luaL_checkinteger(L,4);
    int actual_length = 0;
    int timeout = 0;
    int is_calloc = 0;
    if(lua_isinteger(L,5)) timeout = lua_tointeger(L,5);

    if(!buf){
        if(len>sizeof(read_buf)){
            buf = calloc(len,1);
            is_calloc = 1;
        }
        else buf = read_buf;
    }

    int err = libusb_bulk_transfer(ud,ep,buf,len,&actual_length,timeout);
    lua_pushinteger(L,actual_length);
    lua_pushlstring(L,buf,actual_length);
    lua_pushinteger(L,err);
    if(is_calloc) free(buf);
    return 3;
}

static int lusb_interrupt_transfer(lua_State*L){
    void*ud = lua_touserdata(L,1);
    int ep = luaL_checkinteger(L,2);
    void*buf = 0;
    if(lua_isuserdata(L,3) || lua_islightuserdata(L,3)){
        buf = lua_touserdata(L,3);
    }
    else if(lua_isstring(L,3) && !(ep&LIBUSB_ENDPOINT_IN)){
        buf = (void*)lua_tostring(L,3);
    }
    else{
        // lua_pushstring(L,"transfer data not supported!");
        // lua_error(L);
        // return 1;
        //直接返回字符串数据
    }
    int len = luaL_checkinteger(L,4);
    int actual_length = 0;
    int timeout = 0;
    int is_calloc = 0;
    if(lua_isinteger(L,5)) timeout = lua_tointeger(L,5);

    if(!buf){
        if(len>sizeof(read_buf)){
            buf = calloc(len,1);
            is_calloc = 1;
        }
        else buf = read_buf;
    }

    int err = libusb_interrupt_transfer(ud,ep,buf,len,&actual_length,timeout);
    lua_pushinteger(L,actual_length);
    lua_pushlstring(L,buf,actual_length);
    lua_pushinteger(L,err);
    if(is_calloc) free(buf);
    return 3;
}


static int lusb_control_transfer(lua_State*L){
    void*ud = lua_touserdata(L,1);
    int r_type = luaL_checkinteger(L,2);
    int breq = luaL_checkinteger(L,3);
    int wVal = luaL_checkinteger(L,4);
    int wIdx = luaL_checkinteger(L,5);
    void* buf = lua_touserdata(L,6);
    int len = luaL_checkinteger(L,7);
    int timeout = 0;
    if(lua_isinteger(L,7)) timeout= lua_tointeger(L,7);
    int err = libusb_control_transfer(ud,r_type,breq,wVal,wIdx,buf,len,timeout);
    lua_pushinteger(L,err);
    return 1;
}

